---
title: Two-Factor Authentication (2FA)
description: Enhance your app's security with two-factor authentication.
---

`OTP` `TOTP` `Backup Codes` `Trusted Devices`

Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.

This plugin offers two main methods to do a second factor verification:

1. **OTP (One-Time Password)**: A temporary code sent to the user's email or phone.
2. **TOTP (Time-based One-Time Password)**: A code generated by an app on the user's device.

**Additional features include:**
- Generating backup codes for account recovery
- Enabling/disabling 2FA
- Managing trusted devices

## Installation

<Steps>
    <Step>
        ### Add the plugin to your auth config

        Add the two-factor plugin to your auth configuration and specify your app name as the issuer.

        ```ts title="auth.ts"
        import { betterAuth } from "better-auth"
        import { twoFactor } from "better-auth/plugins" // [!code highlight]

        export const auth = betterAuth({
            // ... other config options
            appName: "My App", // provide your app name. It'll be used as an issuer. // [!code highlight]
            plugins: [
                twoFactor() // [!code highlight]
            ]
        })
        ```
    </Step>
    <Step>
        ### Migrate the database

        Run the migration or generate the schema to add the necessary fields and tables to the database.

        <Tabs items={["migrate", "generate"]}>
            <Tab value="migrate">
            ```package-install
            npx @better-auth/cli migrate
            ```
            </Tab>
            <Tab value="generate">
            ```package-install
            npx @better-auth/cli generate
            ```
            </Tab>
        </Tabs>
        See the [Schema](#schema) section to add the fields manually.
    </Step>

        <Step>
        ### Add the client plugin

        Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor

        ```ts title="auth-client.ts"
        import { createAuthClient } from "better-auth/client"
        import { twoFactorClient } from "better-auth/client/plugins"

        export const authClient = createAuthClient({
            plugins: [
                twoFactorClient()
            ]
        })
        ```
        </Step>
</Steps>

## Usage

### Enabling 2FA

To enable two-factor authentication, call `twoFactor.enable` with the user's password and issuer (optional):

<APIMethod
  path="/two-factor/enable"
  method="POST"
  requireSession
>
```ts
type enableTwoFactor = {
    /**
     * The user's password
     */
    password: string = "secure-password"
    /**
     * An optional custom issuer for the TOTP URI. Defaults to app-name defined in your auth config.
     */
    issuer?: string = "my-app-name"
}
```
</APIMethod>

When 2FA is enabled:
- An encrypted `secret` and `backupCodes` are generated.
- `enable` returns `totpURI` and `backupCodes`.

Note: `twoFactorEnabled` won’t be set to `true` until the user verifies their TOTP code. Learn more about verifying TOTP [here](#totp). You can skip verification by setting `skipVerificationOnEnable` to true in your plugin config.

<Callout type="warn">
Two Factor can only be enabled for credential accounts at the moment. For social accounts, it's assumed the provider already handles 2FA.
</Callout>

### Sign In with 2FA

When a user with 2FA enabled tries to sign in via email, the response object will contain `twoFactorRedirect` set to `true`. This indicates that the user needs to verify their 2FA code.

You can handle this in the `onSuccess` callback or by providing a `onTwoFactorRedirect` callback in the plugin config.

```ts title="sign-in.tsx"
await authClient.signIn.email({
        email: "user@example.com",
        password: "password123",
    },
    {
        async onSuccess(context) {
            if (context.data.twoFactorRedirect) {
                // Handle the 2FA verification in place
            }
        },
    }
)
```

Using the `onTwoFactorRedirect` config:

```ts title="sign-in.ts"
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

const authClient = createAuthClient({
    plugins: [
        twoFactorClient({
            onTwoFactorRedirect(){
                // Handle the 2FA verification globally
            },
        }),
    ],
});
```




<Callout type="warn">
**With `auth.api`**

When you call `auth.api.signInEmail` on the server, and the user has 2FA enabled, it will return an object where `twoFactorRedirect` is set to `true`. This behavior isn’t inferred in TypeScript, which can be misleading. You can check using `in` instead to check if `twoFactorRedirect` is set to `true`.

```ts
const response = await auth.api.signInEmail({
	body: {
		email: "test@test.com",
		password: "test",
	},
});

if ("twoFactorRedirect" in response) {
	// Handle the 2FA verification in place
}
```
</Callout>

### Disabling 2FA

To disable two-factor authentication, call `twoFactor.disable` with the user's password:

<APIMethod
  path="/two-factor/disable"
  method="POST"
  requireSession
>
```ts
type disableTwoFactor = {
    /**
     * The user's password
     */
    password: string
}
```
</APIMethod>

### TOTP

TOTP (Time-Based One-Time Password) is an algorithm that generates a unique password for each login attempt using time as a counter. Every fixed interval (Better Auth defaults to 30 seconds), a new password is generated. This addresses several issues with traditional passwords: they can be forgotten, stolen, or guessed. OTPs solve some of these problems, but their delivery via SMS or email can be unreliable (or even risky, considering it opens new attack vectors).

TOTP, however, generates codes offline, making it both secure and convenient. You just need an authenticator app on your phone.

#### Getting TOTP URI

After enabling 2FA, you can get the TOTP URI to display to the user. This URI is generated by the server using the `secret` and `issuer` and can be used to generate a QR code for the user to scan with their authenticator app.

<APIMethod
  path="/two-factor/get-totp-uri"
  method="POST"
  requireSession
>
```ts
type getTOTPURI = {
    /**
     * The user's password
     */
    password: string
}
```
</APIMethod>


**Example: Using React**

Once you have the TOTP URI, you can use it to generate a QR code for the user to scan with their authenticator app.

```tsx title="user-card.tsx"
import QRCode from "react-qr-code";

export default function UserCard({ password }: { password: string }){
    const { data: session } = client.useSession();
	const { data: qr } = useQuery({
		queryKey: ["two-factor-qr"],
		queryFn: async () => {
			const res = await authClient.twoFactor.getTotpUri({ password });
			return res.data;
		},
		enabled: !!session?.user.twoFactorEnabled,
	});
    return (
        <QRCode value={qr?.totpURI || ""} />
   )
}
```

<Callout>
By default the issuer for TOTP is set to the app name provided in the auth config or if not provided it will be set to `Better Auth`. You can override this by passing `issuer` to the plugin config.
</Callout>

#### Verifying TOTP

After the user has entered their 2FA code, you can verify it using `twoFactor.verifyTotp` method. `Better Auth` follows standard practice by accepting TOTP codes from one period before and one after the current code, ensuring users can authenticate even with minor time delays on their end.

<APIMethod path="/two-factor/verify-totp" method="POST">
```ts
type verifyTOTP = {
    /**
     * The otp code to verify. 
     */
    code: string = "012345"
    /**
     * If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. 
     */
    trustDevice?: boolean = true
}
```
</APIMethod>

### OTP

OTP (One-Time Password) is similar to TOTP but a random code is generated and sent to the user's email or phone.

Before using OTP to verify the second factor, you need to configure `sendOTP` in your Better Auth instance. This function is responsible for sending the OTP to the user's email, phone, or any other method supported by your application.

```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        twoFactor({
          	otpOptions: {
				async sendOTP({ user, otp }, ctx) {
                    // send otp to user
				},
			},
        })
    ]
})
```

#### Sending OTP

Sending an OTP is done by calling the `twoFactor.sendOtp` function. This function will trigger your sendOTP implementation that you provided in the Better Auth configuration.

<APIMethod path="/two-factor/send-otp" method="POST">
```ts
type send2FaOTP = {
    /**
     * If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. 
     */
    trustDevice?: boolean = true
}

if (data) {
    // redirect or show the user to enter the code
}
```
</APIMethod>

#### Verifying OTP

After the user has entered their OTP code, you can verify it

<APIMethod path="/two-factor/verify-otp" method="POST">
```ts
type verifyOTP = {
    /**
     * The otp code to verify. 
     */
    code: string = "012345"
    /**
     * If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. 
     */
    trustDevice?: boolean = true
}
```
</APIMethod>

### Backup Codes

Backup codes are generated and stored in the database. This can be used to recover access to the account if the user loses access to their phone or email.

#### Generating Backup Codes
Generate backup codes for account recovery:

<APIMethod
  path="/two-factor/generate-backup-codes"
  method="POST"
  requireSession
>
```ts
type generateBackupCodes = {
    /**
     * The users password. 
     */
    password: string
}

if (data) {
    // Show the backup codes to the user
}
```
</APIMethod>


<Callout type="warn">
When you generate backup codes, the old backup codes will be deleted and new ones will be generated.
</Callout>

#### Using Backup Codes

You can now allow users to provider backup code as account recover method.


<APIMethod path="/two-factor/verify-backup-code" method="POST">
```ts
type verifyBackupCode = {
    /**
     * A backup code to verify. 
     */
    code: string = "123456"
    /**
     * If true, the session cookie will not be set. 
     */
    disableSession?: boolean = false
    /**
     * If true, the device will be trusted for 30 days. It'll be refreshed on every sign in request within this time. 
     */
    trustDevice?: boolean = true
}
```
</APIMethod>

<Callout>
Once a backup code is used, it will be removed from the database and can't be used again.
</Callout>

#### Viewing Backup Codes

To display the backup codes to the user, you can call `viewBackupCodes` on the server. This will return the backup codes in the response. You should only this if the user has a fresh session - a session that was just created.

<APIMethod
  path="/two-factor/view-backup-codes"
  method="POST"
  isServerOnly
  forceAsBody
>
```ts
type viewBackupCodes = {
    /**
     * The user ID to view all backup codes. 
     */
    userId?: string | null = "user-id"
}
```
</APIMethod>

### Trusted Devices

You can mark a device as trusted by passing `trustDevice` to `verifyTotp` or `verifyOtp`.

```ts
const verify2FA = async (code: string) => {
    const { data, error } = await authClient.twoFactor.verifyTotp({
        code,
        trustDevice: true, // Mark this device as trusted
    })
    if (data) {
        // 2FA verified and device trusted
    }
}
```

When `trustDevice` is set to `true`, the current device will be remembered for 30 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.

### Issuer

By adding an `issuer` you can set your application name for the 2fa application.

For example, if your user uses Google Auth, the default appName will show up as `Better Auth`. However, by using the following code, it will show up as `my-app-name`.

```ts
twoFactor({
    issuer: "my-app-name" // [!code highlight]
})
```
---

## Schema

The plugin requires 1 additional fields in the `user` table and 1 additional table to store the two factor authentication data.

Table: `user`

<DatabaseTable
    fields={[
        { name: "twoFactorEnabled", type: "boolean", description: "Whether two factor authentication is enabled for the user.", isOptional: true },
    ]}
/>

Table: `twoFactor`

<DatabaseTable
    fields={[
        { name: "id", type: "string", description: "The ID of the two factor authentication.", isPrimaryKey: true },
        { name: "userId", type: "string", description: "The ID of the user", isForeignKey: true },
        { name: "secret", type: "string", description: "The secret used to generate the TOTP code.", isOptional: true },
        { name: "backupCodes", type: "string", description: "The backup codes used to recover access to the account if the user loses access to their phone or email.", isOptional: true },
    ]}
/>

## Options

### Server

**twoFactorTable**: The name of the table that stores the two factor authentication data. Default: `twoFactor`.

**skipVerificationOnEnable**: Skip the verification process before enabling two factor for a user.

**Issuer**: The issuer is the name of your application. It's used to generate TOTP codes. It'll be displayed in the authenticator apps.

**TOTP options**

these are options for TOTP.

<TypeTable
  type={{
    digits:{
        description: "The number of digits the otp to be",
        type: "number",
        default: 6,
    },
    period: {
        description: "The period for totp in seconds.",
        type: "number",
        default: 30,
    },
  }}
/>

**OTP options**

these are options for OTP.

<TypeTable
  type={{
    sendOTP: {
        description: "a function that sends the otp to the user's email or phone number. It takes two parameters: user and otp",
        type: "function",
    },
    period: {
        description: "The period for otp in minutes.", 
        type: "number",
        default: 3,
    },
    storeOTP: {
        description: "How to store the otp in the database. Whether to store it as plain text, encrypted or hashed. You can also provide a custom encryptor or hasher.",
        type: "string",
        default: "plain",
    },
  }}
  />

**Backup Code Options**

backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.

<TypeTable
    type={{
        amount: {
            description: "The amount of backup codes to generate",
            type: "number",
            default: 10,
        },
        length: {
            description: "The length of the backup codes",
            type: "number",
            default: 10,
        },
        customBackupCodesGenerate: {
            description: "A function that generates custom backup codes. It takes no parameters and returns an array of strings.",
            type: "function",
        },
        storeBackupCodes: {
            description: "How to store the backup codes in the database. Whether to store it as plain text or encrypted. You can also provide a custom encryptor.",
            type: "string",
            default: "plain",
        },
    }}
/>

### Client

To use the two factor plugin in the client, you need to add it on your plugins list.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"

const authClient =  createAuthClient({
    plugins: [
        twoFactorClient({ // [!code highlight]
            onTwoFactorRedirect(){ // [!code highlight]
                window.location.href = "/2fa" // Handle the 2FA verification redirect // [!code highlight]
            } // [!code highlight]
        }) // [!code highlight]
    ]
})
```


**Options**

`onTwoFactorRedirect`: A callback that will be called when the user needs to verify their 2FA code. This can be used to redirect the user to the 2FA page.
